﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Linq.Expressions;
using System.Reflection;

namespace giaoxuhaison.Common
{
    public class FilterBuilder : IFilterBuilder
    {
        private Dictionary<string, Func<ParameterExpression, MemberExpression, ConstantExpression, Expression>> operations = 
            new Dictionary<string, Func<ParameterExpression, MemberExpression, ConstantExpression, Expression>>();
        
        private IEntityHelper _entityHelper;
        
        public FilterBuilder(IEntityHelper entityHelper)
        {
            _entityHelper = entityHelper;            
            BuildOperationPattern();
        }

        private void BuildOperationPattern()
        {
            operations.Add("cn", GetContainExpression);
            operations.Add("eq", GetEqualExpression);
            operations.Add("lt", GetLessThanExpression);
            operations.Add("gt", GetGreaterThanExpression);
        }

        public Func<T, bool> CreateExpression<T>(string searchField, string value, string operation)
        {
            try
            {
                var propertyName = _entityHelper.GetPropertyNameOfEntityByDisplayName<T>(searchField);
                PropertyInfo propertyInfo = typeof(T).GetProperty(propertyName);
                var param = Expression.Parameter(typeof(T), "x");
                var propertyAccess = Expression.MakeMemberAccess(param, propertyInfo);
                value = (value == null ? string.Empty : value);
                //value = _valueMapper.GetExactlyValueStoreInDatabase<T>(searchField, value);
                var convertedValue = ConvertDataBaseOnType(propertyInfo.PropertyType, value);
                var constantValue = Expression.Constant(convertedValue, propertyInfo.PropertyType);

                var expression = operations[operation];
                var lambdaExpression = Expression.Lambda<Func<T, bool>>(expression(param, propertyAccess, constantValue)
                    , param);

                return (lambdaExpression != null? lambdaExpression.Compile(): null);
            }
            catch (Exception)
            {
                return null;
            }
        }

        public Expression<Func<T, bool>> CreateExpressionForPredicate<T>(string searchField, string value, string operation)
        {
            try
            {
                var propertyName = _entityHelper.GetPropertyNameOfEntityByDisplayName<T>(searchField);
                PropertyInfo propertyInfo = typeof(T).GetProperty(propertyName);
                var param = Expression.Parameter(typeof(T), "x");
                var propertyAccess = Expression.MakeMemberAccess(param, propertyInfo);
                value = (value == null ? string.Empty : value);
                //value = _valueMapper.GetExactlyValueStoreInDatabase<T>(searchField, value);
                var convertedValue = ConvertDataBaseOnType(propertyInfo.PropertyType, value);
                var constantValue = Expression.Constant(convertedValue, propertyInfo.PropertyType);

                var expression = operations[operation];
                var lambdaExpression = Expression.Lambda<Func<T, bool>>(expression(param, propertyAccess, constantValue)
                    , param);

                return (lambdaExpression != null ? lambdaExpression : null);
            }
            catch (Exception)
            {
                return null;
            }
        }

        private object ConvertDataBaseOnType(Type type, string value)
        {
            Type bType = type;
            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                bType = Nullable.GetUnderlyingType(type);
            }

            return Convert.ChangeType(value.ToLower(), type);
        }

        #region Contain

        private Expression GetContainExpression(ParameterExpression param, MemberExpression propertyAccess, ConstantExpression constant)
        {
            if (IsTypeSupportedByContainOperation(propertyAccess.Type))
            {
                if (constant.Value.ToString() == string.Empty)
                {
                    var firstExpression = Expression.Equal(propertyAccess, Expression.Constant(null));
                    var secondExpression = GetStringExpression(param, propertyAccess, constant, false);
                    return Expression.OrElse(firstExpression, secondExpression);
                    
                }
                else
                {
                    var firstExpression = Expression.Not(Expression.Equal(propertyAccess, Expression.Constant(null)));
                    var secondExpression = GetStringExpression(param, propertyAccess, constant, true);
                    return Expression.AndAlso(firstExpression, secondExpression);
                }
            }

            return null;
        }

        private bool IsTypeSupportedByContainOperation(Type dataType)
        {
            return (dataType == typeof(string));
        }

        #endregion

        #region Equal

        private Expression GetEqualExpression(ParameterExpression param, MemberExpression propertyAccess, ConstantExpression constant)
        {
            if (IsTypeSupportedByEqualOperation(propertyAccess.Type))
            {
                if (propertyAccess.Type == typeof(string))
                {
                    var secondExpression = GetStringExpression(param, propertyAccess, constant, false);
                    if (constant.Value.ToString() == string.Empty)
                    {
                        var firstExpression = Expression.Equal(propertyAccess, Expression.Constant(null));
                        return Expression.OrElse(firstExpression, secondExpression);
                    }
                    else
                    {
                        var firstExpression = Expression.Not(Expression.Equal(propertyAccess, Expression.Constant(null)));
                        return Expression.AndAlso(firstExpression, secondExpression);
                    }
                }
                else
                {
                    return Expression.Equal(propertyAccess, constant);
                }
            }

            return null;
        }

        private bool IsTypeSupportedByEqualOperation(Type dataType)
        {
            return (dataType == typeof(string) || dataType == typeof(int) || dataType == typeof(DateTime));
        }

        #endregion

        #region Less than

        private Expression GetLessThanExpression(ParameterExpression param, MemberExpression propertyAccess, ConstantExpression constant)
        {
            if (IsTypeSupportedByLessThanOperation(propertyAccess.Type))
            {
                return Expression.LessThan(propertyAccess, constant);
            }

            return null;
        }

        private bool IsTypeSupportedByLessThanOperation(Type dataType)
        {
            return (dataType == typeof(int) || dataType == typeof(DateTime));
        }

        #endregion

        #region Greater than

        private Expression GetGreaterThanExpression(ParameterExpression param, MemberExpression propertyAccess, ConstantExpression constant)
        {
            if (IsTypeSupportedByGreaterThanOperation(propertyAccess.Type))
            {
                return Expression.GreaterThan(propertyAccess, constant);
            }

            return null;
        }

        private bool IsTypeSupportedByGreaterThanOperation(Type dataType)
        {
            return (dataType == typeof(int) || dataType == typeof(DateTime));
        }

        #endregion

        private Expression GetStringExpression(ParameterExpression param, MemberExpression propertyAccess, ConstantExpression constant, bool isContainMethod)
        {
            var propertyExp = Expression.Property(param, propertyAccess.Member.Name);
            var toLower = Expression.Call(propertyExp,
                                  typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));

            MethodInfo method = typeof(string).GetMethod((isContainMethod ? "Contains" : "Equals"), new[] { typeof(string) });
            return Expression.Call(toLower, method, constant);
        }
    }
}